package me.zhengjie.modules.pay.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wechat.pay.java.core.util.NonceUtil;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.exception.BizException;
import me.zhengjie.modules.order.domain.PayOrder;
import me.zhengjie.modules.biz.domain.Vip;
import me.zhengjie.modules.order.domain.SoldDetail;
import me.zhengjie.modules.order.domain.SoldOrder;
import me.zhengjie.modules.order.mapper.SoldDetailMapper;
import me.zhengjie.modules.order.mapper.SoldOrderMapper;
import me.zhengjie.modules.pay.domain.req.OpenVipReq;
import me.zhengjie.modules.biz.domain.resp.WxPayResp;
import me.zhengjie.modules.order.mapper.PayOrderMapper;
import me.zhengjie.modules.biz.mapper.VipMapper;
import me.zhengjie.modules.order.service.PayOrderService;
import me.zhengjie.modules.pay.domain.req.RefundReq;
import me.zhengjie.modules.pay.domain.req.WxPayReq;
import me.zhengjie.modules.pay.enums.StatusEnum;
import me.zhengjie.modules.pay.service.WxPayService;
import me.zhengjie.modules.shop.domain.ProductKind;
import me.zhengjie.modules.shop.mapper.ProductKindMapper;
import me.zhengjie.modules.shop.service.ProductKindService;
import me.zhengjie.modules.system.domain.WxUser;
import me.zhengjie.modules.system.mapper.WxUserMapper;
import me.zhengjie.utils.RedisUtils;
import me.zhengjie.utils.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static me.zhengjie.modules.biz.constant.GlobalConstant.*;
import static me.zhengjie.modules.pay.domain.constant.WXPayConstants.*;
import static me.zhengjie.modules.pay.domain.constant.WXPayConstants.NOTIFY_URL;
import static me.zhengjie.modules.biz.domain.enums.ErrorCode.*;
import static me.zhengjie.modules.biz.util.OrderSnCreate.createOrderSn;
import static me.zhengjie.modules.biz.util.WxMiniProgramSigner.*;
import static me.zhengjie.modules.base.utils.UnitConvertUtil.priceInCents;

/**
 * 微信支付
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class WxPayServiceImpl implements WxPayService {

    private final VipMapper vipMapper;
    private final WxUserMapper wxUserMapper;
    private final PayOrderService payOrderService;
    private final PayOrderMapper payOrderMapper;
    private final ProductKindService productKindService;
    private final ProductKindMapper productKindMapper;
    private final RedisUtils redisUtils;
    private final SoldOrderMapper soldOrderMapper;
    private final SoldDetailMapper soldDetailMapper;

    /**
     * 开通VIP
     */
    @Override
    @Transactional
    public WxPayResp openVip(OpenVipReq req) throws Exception {
        WxPayResp resp = new WxPayResp();
        // 1.获取需要购买的vip类型
        Vip vip = vipMapper.selectById(req.getVipId());
        if(vip == null){
            throw new BizException(VIP_TYPE_ERROR.getCode(),VIP_TYPE_ERROR.getMessage());
        }
        // 1.1将元转为分
        int total = priceInCents(vip.getPrice());
        // 2检查vip状态
        WxUser wxUser = checkVipStatus(req, vip);
        // 3.创建订单号
        String orderSn = createOrderSn();
        // 4.发送http请求，进行预支付，使用自动更新平台证书的RSA配置,一个商户号只能初始化一个配置，否则会因为重复的下载任务报错
        PrepayResponse response = prepayResponse(total, vip.getName(), orderSn, wxUser);
        // 获取预支付id
        String prepayId = response.getPrepayId();
        // 5.保存订单信息
        PayOrder payOrder = new PayOrder();
        payOrder.setPId(vip.getId());
        payOrder.setOrderSn(orderSn);
        payOrder.setUserId(req.getUserId());
        payOrder.setCreateTime(LocalDateTime.now());
        payOrder.setTradeState(StatusEnum.NOTPAY.getCode());
        payOrder.setTotal(total);
        // 开通vip数量默认1
        payOrder.setNumber(1);
        // 订单过期时间设置到10分钟后（如果没有购买需要返还商品的库存）
        payOrder.setExpireTime(LocalDateTime.now().plusMinutes(10));
        // 备注中标明是vip订单还是商品订单
        payOrder.setRemark(VIP_ORDER);
        payOrder.setOrderType(VIP_ORDER);
        payOrderService.save(payOrder);
        // 6.构建预支付响应
        buildPaymentResponse(prepayId,resp);
        // 将对象存储到redis中,过期时间设置30分钟，定时任务10分钟执行一次，确保待支付能拿到订单的信息
        redisUtils.set(orderSn, resp,30, TimeUnit.MINUTES);
        return resp;
    }

    /**
     * 购买商品,扣除库存
     */
    @Override
    @Transactional
    public WxPayResp buy(String orderSn) throws Exception {
        WxPayResp resp = new WxPayResp();
        // 1.根据订单号查询订单信息
        PayOrder payOrder = payOrderMapper.selectOne(new QueryWrapper<PayOrder>().eq("order_sn", orderSn));
        if(payOrder == null){
            throw new BizException(ORDER_IS_NOT_EXIST.getCode(),ORDER_IS_NOT_EXIST.getMessage());
        }
        // 查看订单是否超时
        if(payOrder.getExpireTime() != null){
            if (LocalDateTime.now().isAfter(payOrder.getExpireTime())) {
                throw new BizException(ORDER_EXPIRE.getCode(),ORDER_EXPIRE.getMessage());
            }
        }
        // 2.获取用户信息
        WxUser wxUser = wxUserMapper.selectById(payOrder.getUserId());
        if(wxUser == null){
            throw new BizException(USER_INFO_ERROR.getCode(),USER_INFO_ERROR.getMessage());
        }
        // 4.发送http请求，进行预支付，使用自动更新平台证书的RSA配置,一个商户号只能初始化一个配置，否则会因为重复的下载任务报错
        PrepayResponse response = prepayResponse(payOrder.getTotal(), payOrder.getOrderType(), orderSn, wxUser);
        // 5.构建预支付响应
        buildPaymentResponse(response.getPrepayId(), resp);
        // 6.将过期时间设置到10分钟后
        payOrderService.update(new UpdateWrapper<PayOrder>()
                .eq("order_sn",orderSn)
                .set("expire_time",LocalDateTime.now().plusMinutes(10))
                .set("trade_state",StatusEnum.NOTPAY.getCode())
        );
        return resp;
    }

    /**
     * 获取订单详情
     */
    @Override
    public void queryOrder(WxPayReq req) throws BizException {
        // 验证请求参数是否为空
        if(req == null){
            throw new BizException(BODY_NOTNULL.getCode(), BODY_NOTNULL.getMessage());
        }
        // 创建微信支付服务
        JsapiService service = WxPayService.createJsapiService();
        // 查询订单
        Transaction transaction;
        if(StringUtils.isNotBlank(req.getOrderSn())) {
            // 根据 outTradeNo 查询订单
            QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
            request.setMchid(MCH_ID);
            request.setOutTradeNo(req.getOrderSn());
            transaction = service.queryOrderByOutTradeNo(request);
        } else if(StringUtils.isNotBlank(req.getTransactionId())) {
            // 根据 transactionId 查询订单
            QueryOrderByIdRequest request = new QueryOrderByIdRequest();
            request.setMchid(MCH_ID);
            request.setTransactionId(req.getTransactionId());
            transaction = service.queryOrderById(request);
        } else {
            // 如果既没有 outTradeNo 也没有 transactionId，则抛出异常
            throw new BizException(PARAM_INVALID.getCode(), PARAM_INVALID.getMessage());
        }
        log.info("订单详情：{}",transaction);
    }

    /**
     * 关闭订单
     */
    @Override
    @Transactional
    public Boolean closeOrder(WxPayReq wxPayReq) throws BizException {
        boolean status = false;
        // 查看用户是否付款，如果已付款需要先退款再改订单状态
        PayOrder payOrder = payOrderService.getOne(new QueryWrapper<PayOrder>().eq("order_sn",wxPayReq.getOrderSn()));
        if(payOrder == null){
            throw new BizException(ORDER_IS_NOT_EXIST.getCode(),ORDER_IS_NOT_EXIST.getMessage());
        }
        // 已付款订单
        if(payOrder.getTransactionId() != null){
            throw new BizException(ORDER_ALREADY_PAY.getCode(),ORDER_ALREADY_PAY.getMessage());
        }
        // 普通订单，先将本地订单的状态待关闭，防止退款后本地异常回滚
        status = payOrderService.update(new UpdateWrapper<PayOrder>()
                .eq("order_sn", wxPayReq.getOrderSn())
                .set("trade_state", StatusEnum.AWAITCLOSED.getCode())
                .set("expire_time",LocalDateTime.now().plusMinutes(10))
        );
        return status;
    }

    @Override
    @Transactional
    public void wxPayNotify(String notifyXml) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        // 解析JSON字符串
        JsonNode jsonNode = objectMapper.readTree(notifyXml);
        String nonce = jsonNode.path("resource").path("nonce").asText();
        String associatedData = jsonNode.path("resource").path("associated_data").asText();
        String ciphertext = jsonNode.path("resource").path("ciphertext").asText();
        // 解密
        String data = decryptResource(ciphertext, API_V3KEY, nonce, associatedData);

        // 获取订单详情
        ObjectMapper resource = new ObjectMapper();
        JsonNode resourceDetail = resource.readTree(data);

        // 微信支付的回调对象
        String openid = resourceDetail.path("payer").path("openid").asText();
        String outTradeNo = resourceDetail.get("out_trade_no").asText();
        String transactionId = resourceDetail.get("transaction_id").asText();
        String tradeState = resourceDetail.get("trade_state").asText();
        // 根据交易状态处理订单
        switch (tradeState) {
            case SUCCESS:
                // 如果订单支付成功
                handleSuccessOrder(outTradeNo, transactionId,openid);
                break;
            case REFUND:
            case CLOSED:
            case REVOKED:
            case USERPAYING:
            case PAYERROR:
                // 订单未完成
                handleUncompletedOrder(outTradeNo, transactionId, tradeState);
                break;
            default:
                log.error("未知的交易状态: " + tradeState);
                break;
        }
    }

    /**
     * 退款情形：
     *  1、普通订单
     *      1.1 商品未付款，取消订单。---直接通过，等待定时任务归还商品库存即可
     *      1.2 商品已付款，但商品还未发货 --- 先修改订单状态，在进行退款，退款成功后，归还库存
     *  2、售后订单
     *      2.1 退款退货 --- 退款后归还库存
     *      2.2 退款不退货 --- 退款，无需补充库存【走普通订单的逻辑，订单状态在关闭订单方法中已修改为退款，定时任务不会修改库存】
     *  3. vip订单
     *      vip订单不支持退款，关闭订单已进行限制
     */
    @Override
    @Transactional
    public Boolean refundOrder(RefundReq req) throws BizException {
        // 创建微信支付服务
        RefundService service = WxPayService.createRefundJsapiService();
        // 初始化商户配置
        CreateRequest request = new CreateRequest();
        // 查询支付订单
        PayOrder payOrder = payOrderMapper.selectOne(new QueryWrapper<PayOrder>().eq("transaction_id", req.getTransactionId()));
        // 1.1 商品未付款，取消订单
        if(payOrder == null){
            return true;
        }
        // 2、售后订单
        SoldOrder soldOrder = soldOrderMapper.selectOne(new QueryWrapper<SoldOrder>().eq("o_id", payOrder.getId()));
        // 如果是售后订单
        if (soldOrder != null && payOrder.getOrderType().equals(SHOPPING_ORDER)) {
            Integer soldType = soldOrder.getSoldType();
            // 售后订单直接改为退款状态
            payOrderService.update(new UpdateWrapper<PayOrder>().eq("order_sn", req.getOrderSn()).set("trade_state", StatusEnum.REFUND.getCode()));
            // 2.1 退款退货
            if (StatusEnum.SOLD_TYPE_TWO.getCode() == soldType) {
                List<SoldDetail> detailList = soldDetailMapper.selectList(new QueryWrapper<SoldDetail>().eq("s_id", soldOrder.getId()));
                // 获取商品列表
                List<Long> kindIds = detailList.stream().map(SoldDetail::getKId).collect(Collectors.toList());
                Map<Long, Integer> map = detailList.stream().collect(Collectors.toMap(SoldDetail::getKId, SoldDetail::getNum));
                List<ProductKind> productKinds = productKindMapper.selectList(new QueryWrapper<ProductKind>().in("id", kindIds));
                productKinds.forEach(kind -> {
                    // 补充库存，修改销量
                    Integer num = map.get(kind.getId());
                    kind.setStock(kind.getStock() + num);
                    kind.setSold(kind.getSold() - num);
                });
                productKindService.updateBatchById(productKinds);
            }
        }
        // 3.设置退款请求参数
        request.setAmount(req.getAmountReq());
        request.setTransactionId(req.getTransactionId());
        request.setOutTradeNo(req.getOrderSn());
        request.setReason("测试微信退款api");
        request.setNotifyUrl(REFUND_NOTIFY_URL);
        request.setOutRefundNo(req.getOutRefundNo());
        // 4.发起退款请求
        try {
            Refund refund = service.create(request);
            log.info("退款信息：{}", refund);
        } catch (RuntimeException e) {
            log.error("退款失败:" + e.getMessage());
            if (e.getMessage().contains("基本账户余额不足，请充值后重新发起")) {
                throw new BizException(NOT_SUFFICIENT_FUNDS.getCode(), NOT_SUFFICIENT_FUNDS.getMessage());
            }
            throw new BizException(REFUND_FAILED.getCode(), REFUND_FAILED.getMessage());
        }
        return true;
    }

    /**
     * 微信退款回调通知
     * @param notifyXml
     */
    @Override
    public void refundNotify(String notifyXml) throws JsonProcessingException {
        ObjectMapper objectMapper = new ObjectMapper();
        // 解析JSON字符串
        JsonNode jsonNode = objectMapper.readTree(notifyXml);
        String nonce = jsonNode.path("resource").path("nonce").asText();
        String associatedData = jsonNode.path("resource").path("associated_data").asText();
        String ciphertext = jsonNode.path("resource").path("ciphertext").asText();
        String data = decryptResource(ciphertext, API_V3KEY, nonce, associatedData);

        // 获取订单详情
        ObjectMapper resource = new ObjectMapper();
        JsonNode resourceDetail = resource.readTree(data);

        // 微信支付的回调对象
        String transactionId = resourceDetail.get("transaction_id").asText();
        String refundStatus = resourceDetail.get("refund_status").asText();

        // 退款成功修改订单状态
        if(Objects.equals(refundStatus, "SUCCESS")){
            payOrderService.update(new UpdateWrapper<PayOrder>()
                    .eq("transaction_id",transactionId)
                    .set("trade_state", StatusEnum.REFUND.getCode())
            );
        }
        log.info("退款详情：{}",resourceDetail);
    }


    /**
     * 判断该用户是否已经是vip了
     * @param req
     * @param vip
     * @return
     * @throws BizException
     */
    private WxUser checkVipStatus(OpenVipReq req, Vip vip) throws BizException {
        // 获取用户信息,如果用户已经是vip，则抛出异常
        WxUser wxUser = wxUserMapper.selectById(req.getUserId());
        if(wxUser == null){
            throw new BizException(USER_INFO_ERROR.getCode(),USER_INFO_ERROR.getMessage());
        }
        boolean isVip = false;
        String vipCode = vip.getVipCode();
        // 检查用户是否已经是对应类型的VIP
        switch (vipCode) {
            case TZ_VIP_CODE:
                isVip = wxUser.getIsTzVip() == 1;
                break;
            case ZSB_VIP_CODE:
                isVip = wxUser.getIsZsbVip() == 1;
                break;
            case DZ_VIP_CODE:
                isVip = wxUser.getIsVip() == 1;
                break;
            default:
                // Handle invalid VIP code case
                break;
        }
        // 如果用户已经是对应类型的VIP，则抛出异常
        if (isVip) {
            throw new BizException(ALREADY_IS_VIP.getCode(), ALREADY_IS_VIP.getMessage());
        }
        return wxUser;
    }

    /**
     * 构建预支付响应对象
     * @param prepayId
     * @param resp
     * @throws Exception
     */
    private void buildPaymentResponse(String prepayId, WxPayResp resp) throws Exception {
        long timestamp = Instant.now().getEpochSecond();
        String nonce = NonceUtil.createNonce(32);
        String packageVal = "prepay_id=" + prepayId;
        // 7.拼接字符串,加密后返回给前端调起支付
        String message = APP_ID + "\n" + timestamp + "\n" + nonce + "\n" + packageVal + "\n";
        String sign = sign(message);
        // 设置签名,小程序id,预支付id,随机字符串,当前时间戳
        resp.setPaySign(sign);
        resp.setAppId(APP_ID);
        resp.setPrepayStr(packageVal);
        resp.setNonceStr(nonce);
        resp.setTimeStamp(String.valueOf(timestamp));
    }


    /**
     * 处理订单支付成功的情况
     *
     * @param outTradeNo
     * @param transactionId
     * @param openid
     */
    private void handleSuccessOrder(String outTradeNo, String transactionId, String openid) {
        WxUser wxUser = wxUserMapper.selectOne(new QueryWrapper<WxUser>().eq("open_id", openid));
        if (wxUser == null) {
            log.error("未找到对应的用户");
            return;
        }
        PayOrder payOrder = payOrderMapper.selectOne(new QueryWrapper<PayOrder>()
                .eq("user_id", wxUser.getId())
                .eq("trade_state", StatusEnum.NOTPAY.getCode())
                .eq("order_sn", outTradeNo)
        );
        if (payOrder == null) {
            log.error("未找到对应的订单");
            return;
        }
        // 根据订单的类型进行处理
        switch (payOrder.getOrderType()){
            case VIP_ORDER:
                vipOrder(payOrder, wxUser);
                break;
            case SHOPPING_ORDER:
                // 暂时没有商品订单的逻辑，预留
                break;
            default:
                log.error("订单类型有误");
        }
        // 修改订单状态为已支付，并保存订单号
        payOrder.setTradeState(StatusEnum.SUCCESS.getCode());
        payOrder.setTransactionId(transactionId);
        payOrder.setSuccessTime(LocalDateTime.now());
        payOrderMapper.updateById(payOrder);
    }

    /**
     * vip订单
     * @param payOrder
     * @param wxUser
     */
    private void vipOrder(PayOrder payOrder, WxUser wxUser) {
        Long pId = payOrder.getPId();
        Vip vip = vipMapper.selectById(pId);
        if (vip == null) {
            log.error("未找到对应的VIP");
        }
        // 荐vip的收货状态改为已收货
        payOrder.setLogisticsStatus(StatusEnum.LOGISTICS_END.getCode());
        // 修改用户的vip状态
        switch (Objects.requireNonNull(vip).getVipCode()) {
            case TZ_VIP_CODE:
                wxUser.setIsTzVip(1);
                break;
            case ZSB_VIP_CODE:
                wxUser.setIsZsbVip(1);
                break;
            case DZ_VIP_CODE:
                wxUser.setIsVip(1);
                break;
            default:
                log.error("VIP类型有误");
        }
        wxUserMapper.updateById(wxUser);
    }


    /**
     * 处理订单未完成的情况
     * @param outTradeNo
     * @param transactionId
     * @param tradeState
     */
    private void handleUncompletedOrder(String outTradeNo, String transactionId, String tradeState) {
        log.info("订单未完成");
        PayOrder payOrder = payOrderMapper.selectOne(new QueryWrapper<PayOrder>()
                .eq("order_sn", outTradeNo));
        if (payOrder != null) {
            // 修改订单状态为对应状态，并保存订单号
            payOrder.setTradeState(StatusEnum.PAYERROR.getCode());
            payOrder.setTransactionId(transactionId);
            payOrder.setSuccessTime(LocalDateTime.now());
            payOrderMapper.updateById(payOrder);
        } else {
            log.error("未找到对应的订单");
        }
    }


    /**
     * 发起预支付请求
     * @param finalPrice
     * @param name(商品名)
     * @param orderSn
     * @param wxUser
     * @return
     */
    private static PrepayResponse prepayResponse(Integer finalPrice, String name, String orderSn, WxUser wxUser) {
        // 创建微信支付服务
        JsapiService service = WxPayService.createJsapiService();
        PrepayRequest request = new PrepayRequest();
        Amount amount = new Amount();
        // 设置商品的价格
        amount.setTotal(finalPrice);
        request.setAmount(amount);
        request.setAppid(APP_ID);
        request.setMchid(MCH_ID);
        // 添加商品名称
        request.setDescription(name);
        // 设置过期时间为当前时间的十分钟后
        LocalDateTime expireTime = LocalDateTime.now().plusMinutes(10);
        Date expireDate = Date.from(expireTime.atZone(ZoneId.systemDefault()).toInstant());
        String formattedExpireTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").format(expireDate);
        request.setTimeExpire(formattedExpireTime);
        // 设置回调url
        request.setNotifyUrl(NOTIFY_URL);
        // 设置订单号
        request.setOutTradeNo(orderSn);
        Payer payer = new Payer();
        // 设置购买人
        payer.setOpenid(wxUser.getOpenId());
        request.setPayer(payer);
        return service.prepay(request);
    }

}
